﻿using Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Caching;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.ServiceModel.Channels;
using System.Xml;
using System.Runtime.Serialization.Json;
using WFI.BLL;
using Models.Base;
using WFIServiceBehaviorExtension.Management;

namespace WFIServiceBehaviorExtension
{ /// <summary>
    /// 用于实现自定义消息分发及验证用户信息
    /// </summary>
    public class ValidateUserNamePasswordBehavior : IDispatchMessageInspector, IEndpointBehavior
    {
        //TODO 这两个参数根据系统的配置处理方式存储，作为示例就直接写了 
        public static int throttleNum = 10; // 限流个数
        public static int throttleUnit = 4; // s

        CacheItemPolicy policy = new CacheItemPolicy();  //！ 过期策略，保证第一个set和之后set的绝对过期时间保持一致
        private static readonly log4net.ILog Logger =log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        #region IDispatchMessageInspector
       
        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            string msg = string.Empty;
            if (!AccessManage._accessManage.IsUserInfoByName(out msg))
            {
                request.Close();
                throw new FaultException(msg);
            }

            // 获取ContractName和OperationName 用来作为缓存键
            var context = OperationContext.Current;
            string contractName = context.EndpointDispatcher.ContractName;

            ////获取传进的消息属性
            //MessageProperties properties = context.IncomingMessageProperties;
            ////获取消息发送的远程终结点IP和端口
            //RemoteEndpointMessageProperty endpoint = properties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
            //string strip = endpoint.Address;

            string operationName = string.Empty;
            if (context.IncomingMessageHeaders.Action == null)
            {
                operationName = request.Properties.Values.LastOrDefault().ToString();
            }
            else
            {
                if (context.IncomingMessageHeaders.Action.Contains("/"))
                {
                    operationName = context.IncomingMessageHeaders.Action.Split('/').LastOrDefault();
                }
            }

            //1.用户是否有权限读取这个
            bool bl = AccessManage._accessManage.IsUserPermissions(contractName + "." + operationName, out msg);
            if(!bl)
            {
                request.Close();
                throw new FaultException(msg);
            }


            if (AccessManage.ThrottleNum > 0)
                throttleNum = AccessManage.ThrottleNum;

            if (AccessManage.ThrottleUnit > 0)
                throttleUnit = AccessManage.ThrottleUnit;

            //判断用户有没有请求权限
            string throttleCacheKey = string.Format("{0}_{1}_{2}", AccessManage.CurrentUser.u_Name, contractName, operationName);
            // 缓存当前请求频率， 以内存缓存System.Runtime.Caching.MemoryCache为例(.net4.0+)
            ObjectCache cache = MemoryCache.Default;
            var requestCount = cache.Get(throttleCacheKey);

            int currRequestCount = 1;
            if (requestCount != null && int.TryParse(requestCount.ToString(), out currRequestCount))
            {
                // 访问次数+1
                currRequestCount++;
                cache.Set(throttleCacheKey, currRequestCount, policy);  //必须保证过期策略和第一次set的时候一致，不然过期时间会有问题
            }
            else
            {
                policy.AbsoluteExpiration = DateTime.Now.AddSeconds(throttleUnit);
                cache.Set(throttleCacheKey, currRequestCount, policy);
            }

            // 如果当前请求数大于阀值，直接关闭
            if (currRequestCount > throttleNum)
            {
                Logger.ErrorFormat("当前:{0}请求数{1}大于阀值{2}/{3}s", AccessManage.CurrentUser.u_Name, currRequestCount, throttleNum, throttleUnit);
                request.Close();
                throw new FaultException("当前请求数大于阀值");
            }
            //作为返回值 传给BeforeSendReply 
            LogInfo log = new LogInfo
            {
                BeginTime = DateTime.Now,
                ContractName = contractName,
                //RequestIP = ,
                UserName = AccessManage.CurrentUser.u_Name,
                OperationName = operationName,
                Request = this.MessageToString(ref request),
                Response = string.Empty
            };
            return log;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            // 补充AfterReceiveRequest 传递过来的日志实体的属性, 记录
            LogInfo log = correlationState as LogInfo;

            if (log == null)
                return;
            log.EndTime = DateTime.Now;
            log.Response = this.MessageToString(ref reply);
            log.Duration = (log.EndTime - log.BeginTime).TotalMilliseconds;
            //attention 为不影响接口性能，日志实体push进队列（redis .etc），然后慢慢落地
            //TODO 这里直接写文本啦~
            try
            {
                //string logPath = "d:\\WcfLog.txt";
                //if (!File.Exists(logPath))
                //{
                //    File.Create(logPath);
                //}
                //StreamWriter writer = new StreamWriter(logPath, true);
                //writer.Write(string.Format("用户：{0}在{1},请求{2}方法,耗时:{3}（毫秒） 请求内容:{4} \r\n ",
                //    log.UserName, log.BeginTime, log.ContractName + "." + log.OperationName, log.Duration, log.Request));
                //writer.Close();

                Logger.InfoFormat("用户：{0}在{1},请求{2}方法,耗时:{3}（毫秒） 请求内容:{4} \r\n ",
                    log.UserName, log.BeginTime, log.ContractName + "." + log.OperationName, log.Duration, log.Request);
            }
            catch (Exception ex) {
                Logger.Error(ex);
            }
        }

        private string MessageToString(ref System.ServiceModel.Channels.Message message)
        {
            if (message == null || message.IsEmpty)
                return "no message";
            WebContentFormat messageFormat = this.GetMessageContentFormat(message);
            MemoryStream ms = new MemoryStream();
            XmlDictionaryWriter writer = null;
            switch (messageFormat)
            {
                case WebContentFormat.Default:
                case WebContentFormat.Xml:
                    writer = XmlDictionaryWriter.CreateTextWriter(ms);
                    break;
                case WebContentFormat.Json:
                    writer = JsonReaderWriterFactory.CreateJsonWriter(ms);
                    break;
                case WebContentFormat.Raw:
                    // special case for raw, easier implemented separately 
                    return this.ReadRawBody(ref message);
            }

            message.WriteMessage(writer);
            writer.Flush();
            string messageBody = Encoding.UTF8.GetString(ms.ToArray());

            // Here would be a good place to change the message body, if so desired. 

            // now that the message was read, it needs to be recreated. 
            ms.Position = 0;

            // if the message body was modified, needs to reencode it, as show below 
            // ms = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); 

            XmlDictionaryReader reader;
            if (messageFormat == WebContentFormat.Json)
            {
                reader = JsonReaderWriterFactory.CreateJsonReader(ms, XmlDictionaryReaderQuotas.Max);
            }
            else
            {
                reader = XmlDictionaryReader.CreateTextReader(ms, XmlDictionaryReaderQuotas.Max);
            }

            System.ServiceModel.Channels.Message newMessage = System.ServiceModel.Channels.Message.CreateMessage(reader, int.MaxValue, message.Version);
            newMessage.Properties.CopyProperties(message.Properties);
            message = newMessage;

            return messageBody;
        }
        private WebContentFormat GetMessageContentFormat(System.ServiceModel.Channels.Message message)
        {
            WebContentFormat format = WebContentFormat.Default;
            if (message.Properties.ContainsKey(WebBodyFormatMessageProperty.Name))
            {
                WebBodyFormatMessageProperty bodyFormat;
                bodyFormat = (WebBodyFormatMessageProperty)message.Properties[WebBodyFormatMessageProperty.Name];
                format = bodyFormat.Format;
            }

            return format;
        }
        private string ReadRawBody(ref System.ServiceModel.Channels.Message message)
        {
            //assemble: System.Runtime.Serialization 
            XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
            bodyReader.ReadStartElement("Binary");
            byte[] bodyBytes = bodyReader.ReadContentAsBase64();
            string messageBody = Encoding.UTF8.GetString(bodyBytes);

            // Now to recreate the message 
            MemoryStream ms = new MemoryStream();
            XmlDictionaryWriter writer = XmlDictionaryWriter.CreateBinaryWriter(ms);
            writer.WriteStartElement("Binary");
            writer.WriteBase64(bodyBytes, 0, bodyBytes.Length);
            writer.WriteEndElement();
            writer.Flush();
            ms.Position = 0;
            XmlDictionaryReader reader = XmlDictionaryReader.CreateBinaryReader(ms, XmlDictionaryReaderQuotas.Max);
            System.ServiceModel.Channels.Message newMessage = System.ServiceModel.Channels.Message.CreateMessage(reader, int.MaxValue, message.Version);
            newMessage.Properties.CopyProperties(message.Properties);
            message = newMessage;

            return messageBody;
        }
        #endregion

        #region IEndpointBehavior

        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new AttachUserNamePasswordInspector());
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new ValidateUserNamePasswordBehavior());
        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }
        #endregion
    }
}
